/**************************************************************************
* This file is part of Hawkengine.
*
* Hawkengine is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Hawkengine is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Hawkengine.  If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/


/**************************************************************************
* Dependencies
**************************************************************************/
#include "engine/app.h"
#include "engine/animation.h"
#include "engine/background.h"
#include "engine/common.h"
#include "engine/engine.h"
#include "engine/image.h"
#include "engine/keyboard.h"
#include "engine/menu.h"
#include "engine/network.h"
#include "engine/render.h"
#include "engine/settings.h"
#include "engine/sound.h"
#include "engine/util.h"

/**************************************************************************
* Preprocessing
**************************************************************************/
#define CIRCLES_TO_DEBUG 4

/**************************************************************************
* Types
**************************************************************************/

/**************************************************************************
* Variables
**************************************************************************/

/**************************************************************************
* Static Variables
**************************************************************************/
static bounds_type engine_bounds = {0, 100, 0, 100};
static BOOL x_flipped = FALSE;
static BOOL y_flipped = FALSE;
static char screenshot_file_name[512];
static float debug_theta = 0;

/**************************************************************************
* Function Prototypes
**************************************************************************/
static void convert_touch(touch_type* touch, bounds_type* bounds);
static void orient_touch(touch_type* touch);
static void update_debug(const touch_type* touch, BOOL proceed);

/**************************************************************************
* Function Implementations
**************************************************************************/
static void convert_touch(touch_type* touch, bounds_type* bounds)
{
  UINT i;

  // adjust scale
  for (i = 0; i < touch->count; i++) {
    bounds_convert_xy(&engine_bounds, bounds,
                      &(touch->locs[i][X]),
                      &(touch->locs[i][Y]));
    bounds_convert_xy_scale(&engine_bounds, bounds,
                            &(touch->delta[i][X]),
                            &(touch->delta[i][Y]));
  }
}

#ifndef EXIT_CUSTOM
void engine_exit(int code)
{
  exit(code);
}
#endif

void engine_init(const bounds_type* bounds)
{
  randomize();
  common_init();
  settings_init();
  sound_init();
  network_init();
  render_init();
  background_init();
  image_init();
  font_init();
  keyboard_init();

  engine_resize(bounds);

  app_init();
  menu_init();

  log_message(LOG_LEVEL_INFO, "Initialization Complete");

  // open the main menu
  menu_open();
}

void engine_navigate(navigation_type navigation)
{
  if (render_showing_message()) {
    render_dismiss_message();
  } else if (menu_active()) {
    menu_navigate(navigation);
  } else {
    app_navigate(navigation);
  }
}

void engine_paint(void)
{
  render_start(&engine_bounds);

  background_render(&engine_bounds);
  if (menu_active()) {
    menu_render(&engine_bounds);
  } else {
    app_render(&engine_bounds);
  }

  if (keyboard_opened()) {
    keyboard_render(&engine_bounds);
  }

  render_stop(&engine_bounds);

  if (strlen(screenshot_file_name) > 0) {
    render_capture(&engine_bounds, screenshot_file_name);
    screenshot_file_name[0] = 0;
  }
}

void engine_parse_args(int argc, char* argv[])
{
  int arg_idx;
  int temp;

  arg_idx = 1;
  while (arg_idx < argc) {
    if ((strcmp(argv[arg_idx], "--log-level") == 0) && ((arg_idx + 1) < argc)) {
      temp = atoi(argv[++arg_idx]);
      level_logged = (log_level_type)_LIMIT(temp, LOG_LEVEL_CRITICAL, LOG_LEVEL_DEBUG);
    } else if ((strcmp(argv[arg_idx], "--debug-level") == 0) && ((arg_idx + 1) < argc)) {
      debugging_level = atoi(argv[++arg_idx]);
    }

    arg_idx++;
  }
}

void engine_quit(BOOL exit, int code)
{
  static BOOL has_quit = FALSE;

  if (!has_quit) {
    settings_save();

    log_message(LOG_LEVEL_INFO, "Application quitting");

    // clean up
    animation_uninit();
    app_uninit();
    image_uninit();
    render_uninit();
    settings_uninit();
    sound_uninit();
    network_uninit();
    common_uninit();

    has_quit = TRUE;
  }

  if (exit) {
    engine_exit(code);
  }
}

void engine_resize(const bounds_type* bounds)
{
  float temp;

  engine_bounds = *bounds;

  // the HawkEngine expects right > left and top > bottom
  if (engine_bounds.left > engine_bounds.right) {
    x_flipped = TRUE;

    temp = engine_bounds.left;
    engine_bounds.left = engine_bounds.right;
    engine_bounds.right = temp;
  } else {
    x_flipped = FALSE;
  }
  if (engine_bounds.bottom > engine_bounds.top) {
    y_flipped = TRUE;

    temp = engine_bounds.bottom;
    engine_bounds.bottom = engine_bounds.top;
    engine_bounds.top = temp;
  } else {
    y_flipped = FALSE;
  }

  app_resize(&engine_bounds);

  render_set_viewport(&engine_bounds);
}

void engine_session_load(void)
{
  log_message(LOG_LEVEL_INFO, "Trying to load session");

  if (app_session_load()) {
    log_message(LOG_LEVEL_INFO, "Session loaded.");

    animation_finish();
    menu_close();
    menu_pause();
  }
}

void engine_session_save(void)
{
  log_message(LOG_LEVEL_INFO, "Saving session");

  animation_finish();
  app_session_save();
}

void engine_take_screenshot(const char* file_name)
{
  log_message(LOG_LEVEL_INFO, "Taking screenshot");

  strcpy(screenshot_file_name, file_name);
}

void engine_touch_move(const touch_type* touch)
{
  touch_type adjusted_touch;

  adjusted_touch = (*touch);
  orient_touch(&adjusted_touch);

  update_debug(&adjusted_touch, TRUE);

  if (keyboard_opened()) {
    convert_touch(&adjusted_touch, &keyboard_bounds);
    keyboard_touch_move(&adjusted_touch);
  } else if (menu_active()) {
    // in menu
    convert_touch(&adjusted_touch, &menu_bounds);
    menu_touch_move(&adjusted_touch);
  } else {
    // in game
    convert_touch(&adjusted_touch, &app_bounds);
    app_touch_move(&adjusted_touch);
  }
}

void engine_touch_start(const touch_type* touch)
{
  touch_type adjusted_touch;

  if (touch->count <= 0) {
    return;
  }

  adjusted_touch = (*touch);
  orient_touch(&adjusted_touch);

  if (render_showing_message()) {
    render_dismiss_message();
  } else if (keyboard_opened()) {
    // keyboard open
    convert_touch(&adjusted_touch, &keyboard_bounds);
    keyboard_touch_start(&adjusted_touch);
  } else if (menu_active()) {
    // in menu
    convert_touch(&adjusted_touch, &menu_bounds);
    menu_touch_start(&adjusted_touch);
  } else {
    // in game
    convert_touch(&adjusted_touch, &app_bounds);
    app_touch_start(&adjusted_touch);
  }
}

void engine_touch_stop(const touch_type* touch)
{
  touch_type adjusted_touch;

  adjusted_touch = (*touch);
  orient_touch(&adjusted_touch);

  update_debug(&adjusted_touch, FALSE);

  if (keyboard_opened()) {
    // keyboard open
    convert_touch(&adjusted_touch, &keyboard_bounds);
    keyboard_touch_stop(&adjusted_touch);
  } else if (menu_active()) {
    // in menu
    convert_touch(&adjusted_touch, &menu_bounds);
    menu_touch_stop(&adjusted_touch);
  } else {
    // in game
    convert_touch(&adjusted_touch, &app_bounds);
    app_touch_stop(&adjusted_touch);
  }
}

void engine_update(float secs)
{
  // general updates
  background_update(secs);
  render_update(secs);
  sound_update(secs);
  animation_update(secs);

  // animate all tasks
  if (!(animation_pending())) {
    // update menu or game depending on situation
    if (menu_active()) {
      menu_update(secs);
    } else {
      app_update(secs);
    }
  }
}

static void orient_touch(touch_type* touch)
{
  UINT i;

  // handle flipping
  if (x_flipped) {
    for (i = 0; i < touch->count; i++) {
      touch->locs[i][X] = (engine_bounds.left +
                          (engine_bounds.right - touch->locs[i][X]));
      touch->delta[i][X] *= -1;
    }
  }
  if (y_flipped) {
    for (i = 0; i < touch->count; i++) {
      touch->locs[i][Y] = (engine_bounds.bottom +
                          (engine_bounds.top - touch->locs[i][Y]));
      touch->delta[i][Y] *= -1;
    }
  }
}

static void update_debug(const touch_type* touch, BOOL proceed)
{
  static touch_type last_touch;
  static float temp_debugging_level = 0;
  bounds_type scaled_unit_bounds;
  touch_type adjusted_touch;
  float v1[3], v2[3], v3[3], theta;
  int i;

  // convert the touch to scaled unit bounds
  scaled_unit_bounds = UNIT_BOUNDS;
  bounds_keep_aspect(&engine_bounds, &scaled_unit_bounds);
  adjusted_touch = (*touch);
  convert_touch(&adjusted_touch, &scaled_unit_bounds);

  if (proceed) {
    if ((temp_debugging_level - debugging_level) < 0.5) {
      // initial circling part
      for (i = 0; i < 2; i++) {
        v1[i] = last_touch.locs[0][i];
        v2[i] = adjusted_touch.locs[0][i];
      }
      v2[Z] = v1[Z] = 0;
      theta = angle_between(v1, v2);
      cross_product(v1, v2, v3);
      if (v3[Z] < 0) {
        debug_theta += theta;
      } else if (v3[Z] > 0) {
        debug_theta -= theta;
      }
    }
  } else {
    // handle current debugging_level
    int last_debugging_level = debugging_level;
    if ((temp_debugging_level - debugging_level) < 0.5) {
      if (fabs(debug_theta / (2.0 * PI)) > CIRCLES_TO_DEBUG) {
        if (debug_theta > 0) {
          // increasing debugging level
          temp_debugging_level += 0.5;
        } else {
          // decreasing debugging level
          temp_debugging_level -= 1;
        }
      }

      // clear theta now that it's used or wasn't enough
      debug_theta = 0;
    } else if ((temp_debugging_level - debugging_level) < 1) {
      float corner_pt[2];
      if ((temp_debugging_level - debugging_level) < 0.75) {
        corner_pt[X] = engine_bounds.right;
      } else {
        corner_pt[X] = engine_bounds.left;
      }
      corner_pt[Y] = engine_bounds.top;
      if (get_distance((float*)corner_pt, (float*)(touch->locs[0]), 2) <
          (_MIN(bounds_width(&engine_bounds),
                bounds_height(&engine_bounds)) / 4.0)) {
        temp_debugging_level += (0.25 + EPSILON);
      } else {
        // reset incremental debugging adjustment
        temp_debugging_level = debugging_level;
      }
    }

    debugging_level = ((int)temp_debugging_level);
    _UPDATE_MAX(debugging_level, 0);
    _UPDATE_MIN(debugging_level, MAX_DEBUGGING_LEVEL);
    if (last_debugging_level != debugging_level) {
      log_message(LOG_LEVEL_INFO, "Debugging Level changed to: %d",
                  debugging_level);
      // reset incremental debugging adjustment
      temp_debugging_level = debugging_level;
    }

    if (debugging_level > 0) {
      level_logged = LOG_LEVEL_DEBUG;
    } else {
      level_logged = LOG_LEVEL_WARNING;
    }

    memset(&last_touch, 0, sizeof(last_touch));
  }

  last_touch = adjusted_touch;
}
